/*  dreic -- a compiler for Drei
 *
 *  $Id: Code.scala 3322 2007-01-08 16:39:28Z mihaylov $
 */

package dreic

import java.io.PrintWriter

class Code {
  import RISC._
  import scala.collection.mutable.ArrayBuffer

  //########################################################################
  // Instance variables

  /** Already emitted instructions */
  private var code = new ArrayBuffer[Instruction]()

  /** Stack frame size */
  private var frameSize = 0

  /* Program counter */
  private var _pc: Int = 0

  //########################################################################
  // Methods - address handling

  /** Return address of next instruction. */
  def pc: Int = _pc

  //########################################################################
  // Methods - frame handling

  /** Increment frame size by given value. */
  def incFrameSize(bytes: Int) =
    frameSize = frameSize + bytes

  /** Decrement frame size by given value. */
  def decFrameSize(bytes: Int): Unit = {
    frameSize = frameSize - bytes
    assert(frameSize >= 0)
  }

  /** Return current frame size. */
  def getFrameSize: Int = frameSize

  //########################################################################
  // Methods - code emitting

  def emit(opcode: Int): Unit =
    emit(opcode, Integer.MIN_VALUE)

  def emit(opcode: Int, c: Int): Unit =
    emit(opcode, Integer.MIN_VALUE, c)

  def emit(opcode: Int, c: Int, s: String): Unit =
    emit(opcode, Integer.MIN_VALUE, Integer.MIN_VALUE, c, s)

  def emit(opcode: Int, l: Label): Unit =
    emit(opcode, Integer.MIN_VALUE, l)

  def emit(opcode: Int, l: Label, s: String): Unit =
    emit(opcode, Integer.MIN_VALUE, l, s)

  def emit(opcode: Int, a: Int, c: Int): Unit =
    emit(opcode, a, Integer.MIN_VALUE, c)

  def emit(opcode: Int, a: Int, c: Int, s: String): Unit =
    emit(opcode, a, Integer.MIN_VALUE, c, s)

  def emit(opcode: Int, a: Int, l: Label): Unit =
    emit(opcode, a, l, null)

  def emit(opcode: Int, a: Int, l: Label, s: String): Unit =
    emit(newInstruction(opcode, a, Integer.MIN_VALUE, l, s))

  private def syscall2string(c: Int): String = c match {
    case SYS_IO_RD_CHR => "SYS_IO_RD_CHR"
    case SYS_IO_RD_INT => "SYS_IO_RD_INT"
    case SYS_IO_WR_CHR => "SYS_IO_WR_CHR"
    case SYS_IO_WR_INT => "SYS_IO_WR_INT"
    case SYS_GC_INIT   => "SYS_GC_INIT"
    case SYS_GC_ALLOC  => "SYS_GC_ALLOC"
    case SYS_GET_TOTAL_MEM_SIZE => "SYS_GET_TOTAL_MEM_SIZE"
    case SYS_EXIT      => "SYS_EXIT"
    case _             => Integer.toString(c)
  }

  def emit(opcode: Int, a: Int, b: Int, c: Int): Unit = {
    val s = if (opcode == SYSCALL) syscall2string(c) else null
    emit(opcode, a, b, c, s)
  }

  def emit(opcode: Int, a: Int, b: Int, c: Int, s: String): Unit =
    emit(newInstruction(opcode, a, b, c, s))

  def emit(i: Instruction) = {
    code += i
    _pc = _pc + WORD_SIZE
  }

  //########################################################################
  // Methods - program printing.

  /** Print program. */
  def write(out: PrintWriter): Unit = {
    code.foreach(.write(out))
    out.flush()
  }

  //########################################################################
  // Labels

  private var labelCount: Int = 0

  def getLabel: Label = {
    labelCount = labelCount + 1
    new Label("L" + labelCount)
  }
  def getLabel(name: String) = new Label(name)

  class Label(name: String) extends Instruction {
    var isAnchored: Boolean = false
    def setAnchor: Unit = {
      assert(!isAnchored)
      code += this
      isAnchored = true
    }
    override def toString() = name
    def write(out: PrintWriter): Unit = {
      out.print(name)
      out.println(":")
    }
  }

  //########################################################################
  // Instructions

  abstract sealed class Instruction {

    def write(out: PrintWriter): Unit

    protected def writeInstr(out: PrintWriter, opcode: Int, a: Int, b: Int,
                             c: Any, s: String): Unit =
    {
      var cnt = 0
      def writeInField(x: Any, l: Int): Unit = {
        val s = x.toString
        out.print(s)
        for (val i <- 0 until (l - s.length))
          out.print(' ')
        cnt = cnt + l
      }
      out.print("    ")
      writeInField(mnemonics(opcode), 9)
      if (a != Integer.MIN_VALUE) writeInField(a, 3)
      if (b != Integer.MIN_VALUE) writeInField(b, 3)
      if (c != null) {
        val s = c.toString
        out.print(s)
        cnt = cnt + s.length
      }
      if (s != null) {
        while (cnt < 23) { out.print(' '); cnt = cnt + 1 }
        out.print("/* ")
        out.print(s)
        out.print(" */")
      }
      out.println()
    }

  }

  private def newInstruction(opcode: Int, a: Int, b: Int, c: Int, s: String) =
    new Instruction {
      def write(out: PrintWriter): Unit =
        writeInstr(out, opcode, a, b, if (c != Integer.MIN_VALUE) c else null, s)
    }

  private def newInstruction(opcode: Int, a: Int, b: Int, c: Label, s: String) =
    new Instruction {
      def write(out: PrintWriter): Unit =
        writeInstr(out, opcode, a, b, c, s)
    }

  //########################################################################
}
